﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Runtime.Serialization;
using System.Security.Permissions;
using GRV11;
using System.Windows.Media.Imaging;

namespace gpuimp.core
{
    [Serializable]
    public class Canvas
    {
        private int fileID;
        private int id;
        private float zoom;
        private Point imageSize;
        private Point canvasArea;
        private Point position;
        private string name;
        private Bitmap thumbnail;
        private BitmapSource thumbnailSource;

        public GRTexture grThumbnail;

//        private List<Point> selection;

        private StateManager stateManager;
        private LayerManager layerManager;

        [NonSerialized]
        private GR gr;

        private bool tiltCamera;

        public int[] FBO;
        public GRTexture grFBOTexture;

        public Selection selection;
        
#region Properties

        public Point Position
        {
            get { return position; }
            set { position = value; }
        }

        /// <summary>
        /// ID number for the file current considered "default" for this canvas
        /// </summary>
        public int FileID
        {
            get { return fileID; }
            set { fileID = value; }
        }

        /// <summary>
        /// ID number for this canvas
        /// </summary>
        public int ID
        {
            get { return id; }
            set { id = value; }
        }

        /// <summary>
        /// Zoom level of the canvas, 1.0 should be one pixel on canvas to one pixel on screen
        /// </summary>
        public float Zoom
        {
            get { return zoom; }
            set 
            { 
                zoom = value;
                if (zoom <= 0)
                    zoom = 0.01f;
            }
        }

        /// <summary>
        /// X and Y dimensions in pixels of the canvas
        /// </summary>
        public Point ImageSize
        {
            get { return imageSize; }
            set 
            { 
                //should eventually resize as appropriate probably
                imageSize = value; 
            }
        }

        /// <summary>
        /// Sets the area of the control the canvas has available to it to render, width and height
        /// </summary>
        public Point CanvasArea
        {
            get { return canvasArea; }
            set { canvasArea = value; }
        }

        /// <summary>
        /// Name for this canvas, generally same as filename
        /// </summary>
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        /// <summary>
        /// Thumbnail representation of the canvas for tabs, etc
        /// </summary>
        public Bitmap Thumbnail
        {
            get 
            {
                grThumbnail.SetAsActiveTexture(gr);
                grThumbnail.TransferOpenGLTextureDataBackToHostMemoryAndCopyToCompatibleBitmap(gr, thumbnail);

                return thumbnail; 
            }
        }

        /// <summary>
        /// Geometric selection on canvas
        /// </summary>
        public Selection Selection //List<Point> Selection
        {
            get { return selection; }
            set { selection = value; }
        }

        /// <summary>
        /// Layer Manager for this canvas
        /// </summary>
        public LayerManager LayerManager
        {
            get { return layerManager; }
        }

        /// <summary>
        /// StateManager for this canvas
        /// </summary>
        public StateManager StateManager
        {
            get { return stateManager; }
        }

        /// <summary>
        /// If true, angles the camera to show the layer stack from the side rather than straight down
        /// </summary>
        public bool TiltCamera
        {
            get { return tiltCamera; }
            set { tiltCamera = value; }

        }

        public GR GR
        {
            get { return gr; }
        }

        // A method called when serializing a Singleton.
        [SecurityPermissionAttribute(SecurityAction.LinkDemand,
        Flags = SecurityPermissionFlag.SerializationFormatter)]
       /* void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            // Instead of serializing this object, 
            // serialize a SingletonSerializationHelp instead.
            info.SetType(typeof(Canvas));
            // No other values need to be added.
        }
       */

#endregion

#region Constructors/Deconstructors
        /// <summary>
        /// Create a new canvas instance
        /// </summary>
        /// <param name="size">x and y dimension of the canvas</param>
        /// /// <param name="grControl">the opengl control used in the gui to render the canvas</param>
        public Canvas(GRControl grControl, Point size)
        {
            gr = grControl.GetGR();
            if (gr == null) return; //this shouldnt happen

            fileID = -1;
            Random rand = new Random();
            id = rand.Next(); //should maintain a list of used ids somewhere for this instead
            zoom = 1f;
            name = "Canvas";

            imageSize = size;
            thumbnail = new Bitmap(imageSize.X, imageSize.Y);
            grThumbnail.CreateTextureFromBitmap(gr, thumbnail, false);
            //selection = new List<Point>();
            //stateManager = new StateManager();
            layerManager = new LayerManager(this);
            stateManager = new StateManager(id);

            grControl.GetGR().glEnable(GR.GL_TEXTURE_2D);
            SetupFBO();

            selection = new Selection(this);
        }

        /// <summary>
        /// Create a canvas instance with specific id's and name
        /// </summary>
        /// <param name="size">x and y dimension of the canvas</param>
        /// <param name="id">ID used to identify the canvas</param>
        /// <param name="fileID">ID used to identify the file associated with the canvas as its default</param>
        /// <param name="name">Name shown on tabs, etc for the canvas, typically the filename</param>
        public Canvas(Point size, int id, int fileID, string name)
        {
            this.gr = CanvasManager.gr;
            this.fileID = fileID;
            this.id = id; //should maintain a list of used ids somewhere for this instead
            zoom = 1.0f;
            this.name = name;

            imageSize = size;
            thumbnail = new Bitmap(imageSize.X, imageSize.Y);
            grThumbnail = new GRTexture();
            grThumbnail.CreateTextureFromBitmap(gr, thumbnail, false);
            //selection = new List<Point>();
            //stateManager = new StateManager();
            layerManager = new LayerManager(this);
            stateManager = new StateManager(id);

            try
            {
                CanvasManager.gr.glEnable(GR.GL_TEXTURE_2D);
            }
            catch (NullReferenceException nfe)
            {
                Console.WriteLine(nfe.Message);
            }
            
            SetupFBO();
            selection = new Selection(this);
           // SetupOrthoView();
        }

        public Canvas(Point size, int fileID, string name)
        {
            this.gr = CanvasManager.gr;
            this.fileID = fileID;
            zoom = 1.0f;
            this.name = name;
            Random rand = new Random();
            id = rand.Next(); //should maintain a list of used ids somewhere for this instead

            imageSize = size;
            thumbnail = new Bitmap(imageSize.X, imageSize.Y);
            grThumbnail = new GRTexture();
            grThumbnail.CreateTextureFromBitmap(gr, thumbnail, false);
            //selection = new List<Point>();
            //stateManager = new StateManager();
            layerManager = new LayerManager(this);
            stateManager = new StateManager(id);

            try
            {
                CanvasManager.gr.glEnable(GR.GL_TEXTURE_2D);
            }
            catch (NullReferenceException nfe)
            {
                Console.WriteLine(nfe.Message);
            }

            SetupFBO();

            selection = new Selection(this);
            // SetupOrthoView();
        }

        /*public Canvas(SerializationInfo information, StreamingContext context)
        {
            this.gr = CanvasManager.gr;
            this.zoom = 0.5f;
            this.name = (string)information.GetValue("name", typeof(string));
            thumbnail = new Bitmap(128, 128);
            imageSize = (Point)information.GetValue("ImageSize", typeof(Point));
            selection = new List<Point>();
            layerManager = (LayerManager)information.GetValue("LayerManager", typeof(LayerManager));

            CanvasManager.gr.glEnable(GR.GL_TEXTURE_2D);

            SetupFBO();
        }*/

        ~Canvas()
        {
        }
#endregion

#region Methods
        public void SetupFBO()
        {
            //have to flipflop to modify and then copy texture back over
            FBO = new int[2];
            CanvasManager.gr.glEnable(GR.GL_TEXTURE_2D);

            CanvasManager.gr.glGenFramebuffersEXT(2, FBO);
            CanvasManager.gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, FBO[0]);
            grFBOTexture = new GRTexture();
            grFBOTexture.CreateTextureFromBitmap(CanvasManager.gr, new Bitmap(imageSize.X, imageSize.Y), false);//.CreateCheckerboardTexture(CanvasManager.gr, imageSize.X, imageSize.Y);
            CanvasManager.gr.glFramebufferTexture2DEXT(GR.GL_FRAMEBUFFER_EXT, GR.GL_COLOR_ATTACHMENT0_EXT, GR.GL_TEXTURE_2D, grFBOTexture.GetOpenGLTextureID(), 0);
            CanvasManager.gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, 0);
            Console.WriteLine(CanvasManager.gr.glCheckFramebufferStatusEXT(FBO[0]) + " " + GR.GL_FRAMEBUFFER_COMPLETE_EXT);
            
            int[] depthBuffer = new int[1];
            CanvasManager.gr.glGenRenderbuffersEXT(1, depthBuffer);
            CanvasManager.gr.glBindRenderbufferEXT(GR.GL_RENDERBUFFER_EXT, depthBuffer[0]);
            CanvasManager.gr.glRenderbufferStorageEXT(GR.GL_RENDERBUFFER_EXT, GR.GL_DEPTH_COMPONENT, (int)imageSize.X, (int)imageSize.Y);
            CanvasManager.gr.glFramebufferRenderbufferEXT(GR.GL_FRAMEBUFFER_EXT, GR.GL_DEPTH_ATTACHMENT_EXT, GR.GL_RENDERBUFFER_EXT, depthBuffer[0]);
        }

        public void SetupOrthoView()
        {
            gr.glViewport(0, 0, canvasArea.X, canvasArea.Y);
            
            // PROJECTION matrix
            gr.glMatrixMode(GR.GL_PROJECTION);
            gr.glLoadIdentity();

            int halfWidth = canvasArea.X;//imageSize.X;
            int halfHeight =canvasArea.Y; //imageSize.Y;// *2;
            gr.glOrtho(-halfWidth, halfWidth, -halfHeight, halfHeight, 100, -0.1);

            // MODELVIEW matrix, typically used to transform individual models
            gr.glMatrixMode(GR.GL_MODELVIEW);
            gr.glLoadIdentity();
        }
        
        /// <summary>
        /// Handles all the necessary draw setup, then calls draw on the layer manager
        /// </summary>
        public void Draw(GRControl control)
        {
            if (gr == null)
                gr = control.GetGR();

            if (gr != null)
            {
                //gather finaltextures after modifiers are applied
                layerManager.DrawModifiers(gr);

                //now render as normal
                canvasArea.X = control.Width;
                canvasArea.Y = control.Height;
                // Viewport 
               // gr.glViewport(0, 0, imageSize.X, imageSize.Y);
                gr.glEnable(GR.GL_BLEND);
                gr.glBlendFunc(GR.GL_SRC_ALPHA, GR.GL_ONE_MINUS_SRC_ALPHA);
                gr.glColorMask(GR.GL_TRUE, GR.GL_TRUE, GR.GL_TRUE, GR.GL_TRUE);
                // Clear the viewport
                gr.glClearColor(0f, 0f, 0f, 0f);
                gr.glClear(GR.GL_COLOR_BUFFER_BIT | GR.GL_DEPTH_BUFFER_BIT);
                
                // Basic rendering conditions
                gr.glEnable(GR.GL_DEPTH_TEST);
                gr.glDepthFunc(GR.GL_LEQUAL);
                gr.glEnable(GR.GL_CULL_FACE);
                gr.glCullFace(GR.GL_BACK);
                gr.glFrontFace(GR.GL_CCW);

                SetupOrthoView();

                // Preserve current matrix for the active matrix stack (in this case the MODELVIEW matrix)
                gr.glPushMatrix();


                float layerSpacing = 0.5f;
                if (tiltCamera)
                {
                    gr.glRotatef(0.3f, 1, 0, 0); //rotate the view a bit so we see it from the side down
                    layerSpacing = 2.0f;
                }

                gr.glScalef(Zoom, Zoom, 1);

                //move to position
                gr.glTranslatef
                (
                    Position.X,
                    Position.Y,
                    0
                );

                gr.glPushMatrix();
                gr.glTranslatef(0, 0, (layerManager.Layers.Count + 1) * (-layerSpacing));
                selection.Draw(gr);
                gr.glPopMatrix();

                //pass draw command to layer manager now that its all setup
                layerManager.Draw(gr, layerSpacing);

                // Restore the previously-preserved matrix for the active matrix stack (in this case the MODELVIEW matrix)
                gr.glPopMatrix();

                grThumbnail.SetAsActiveTexture(gr);
                gr.glCopyTexSubImage2D(GR.GL_TEXTURE_2D, 0, 0, 0, 0, 0, imageSize.X, imageSize.Y);
                gr.wglSwapBuffers(control.GetHDC());

                //copy to the thumbnail image
                
            }
        }

        /*public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("Name", this.name);
            info.AddValue("ImageSize", this.imageSize);
            info.AddValue("LayerManager", this.layerManager);
        }*/
#endregion

        public BitmapSource ThumbnailSource
        {
            get { return loadBitmap(Thumbnail); }
            set { thumbnailSource = value; }
        }

        public static BitmapSource loadBitmap(System.Drawing.Bitmap source)
        {
            return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(source.GetHbitmap(), IntPtr.Zero, System.Windows.Int32Rect.Empty,
                System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
        }

    }
}
